﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Notoric.Commanding;
using Notoric.ViewModel;
using Notoric.Commanding.CommandBase;

namespace Notoric.Gui.Windows
{
    /// <summary>
    /// Interaction logic for NewScoreDialog.xaml
    /// </summary>
    public partial class NewScoreDialog : Window
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="NewScoreDialog"/> class.
        /// </summary>
        public NewScoreDialog()
        {
            PresentedCompositions = new ObservableCollection<CompositionInfo>();
            InitializeComponent();
        }

        #endregion

        #region Dependency Property Fields

        /// <summary>
        /// Identifies the <see cref="Compositions"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty CompositionsProperty = DependencyProperty.Register(
            "Compositions", typeof(IProjectExplorerItem), typeof(NewScoreDialog)
            );

        /// <summary>
        /// Identifies the <see cref="IsNewScore"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty IsNewScoreProperty = DependencyProperty.Register(
            "IsNewScore", typeof(bool), typeof(NewScoreDialog), new PropertyMetadata(true)
            );

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the root of the compositions hierarchy.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="IProjectExplorerItem"/>
        /// </para>
        /// <para>
        /// The root of the compositions hierarchy.
        /// </para>
        /// </value>
        public IProjectExplorerItem Compositions
        {
            get { return (IProjectExplorerItem)GetValue(CompositionsProperty); }
            set { SetValue(CompositionsProperty, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the edited score has just been created.
        /// </summary>
        /// <value>
        /// 	<see langword="true"/> if the score has just been created; 
        /// 	<see langword="false"/> if it is an existing score.
        /// 	The default value is <see langword="true"/>.
        /// </value>
        public bool IsNewScore
        {
            get { return (bool)GetValue(IsNewScoreProperty); }
            set { SetValue(IsNewScoreProperty, value); }
        }

        /// <summary>
        /// Gets or sets the presented compositions.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ObservableCollection{T}"/> of <see cref="CompositionInfo"/>
        /// </para>
        /// <para>
        /// The collection of compositions that will be presented in the created score.
        /// </para>
        /// </value>
        public ObservableCollection<CompositionInfo> PresentedCompositions
        {
            get;
            private set;
        }

        #endregion
        
        #region Event Handlers

        /// <summary>
        /// Handles the Checked event of the RadioButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void RadioButton_Checked(object sender, RoutedEventArgs e)
        {
            if (!this.IsLoaded)
                return;

            if (PART_BlankScore.IsChecked == true)
            {
                PART_Next.IsEnabled = false;
                PART_Finish.IsEnabled = true;
            }
            else
            {
                PART_Next.IsEnabled = true;
                PART_Finish.IsEnabled = false;
            }
        }

        /// <summary>
        /// Handles the Click event of the PART_Next control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void PART_Next_Click(object sender, RoutedEventArgs e)
        {
            PART_FirstPage.Visibility = System.Windows.Visibility.Collapsed;
            PART_SecondPage.Visibility = System.Windows.Visibility.Visible;
            PART_Previous.IsEnabled = true;
            PART_Next.IsEnabled = false;
            PART_Finish.IsEnabled = true;
        }

        /// <summary>
        /// Handles the Click event of the PART_Previous control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void PART_Previous_Click(object sender, RoutedEventArgs e)
        {
            PART_SecondPage.Visibility = System.Windows.Visibility.Collapsed;
            PART_FirstPage.Visibility = System.Windows.Visibility.Visible;
            PART_Previous.IsEnabled = false;
            PART_Next.IsEnabled = true;
            PART_Finish.IsEnabled = false;
        }

        /// <summary>
        /// Handles the Click event of the PART_Finish control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void PART_Finish_Click(object sender, RoutedEventArgs e)
        {
            var score = DataContext as ViewModel.Elements.Score;

            if (PART_Generate.IsChecked == true)
            {
                var pages = score.Pages.Count;

                var generationMacro = new UndoableMacroCommand(score.UndoStack, score.RedoStack)
                {
                    UseStacks = false
                };

                foreach (var composition in PresentedCompositions)
                {
                    if (composition.FirstMeasure < 1)
                        composition.FirstMeasure = 1;
                    else if (composition.FirstMeasure > composition.Composition.MeasuresCount)
                        composition.FirstMeasure = (ushort)composition.Composition.MeasuresCount;
                    if (composition.LastMeasure < composition.FirstMeasure)
                        composition.LastMeasure = composition.FirstMeasure;
                    else if (composition.LastMeasure > composition.Composition.MeasuresCount)
                        composition.LastMeasure = (ushort)composition.Composition.MeasuresCount;
                    if (composition.MeasuresPerSystem < 1)
                        composition.MeasuresPerSystem = 1;
                    if (composition.SystemsPerPage < 1)
                        composition.SystemsPerPage = 1;

                    if (!ViewModel.Elements.MusicSection.ValidCompositionPart(composition.Composition, composition.Parts, composition.FirstMeasure, composition.LastMeasure))
                    {
                        MessageBox.Show(
                            string.Format(
                                "The selected composition part: \"{0}\", measures {1} to {2} cannot be presented in a music section, because it contains empty measures. Please, select another part of the composition or fill the measures first.",
                                composition.Composition.DisplayName,
                                composition.FirstMeasure,
                                composition.LastMeasure
                                ),
                            "Notoric",
                            MessageBoxButton.OK,
                            MessageBoxImage.Exclamation
                            );
                        return;
                    }

                    var measuresCount = composition.LastMeasure - composition.FirstMeasure + 1;
                    var measuresPerPage = composition.MeasuresPerSystem * composition.SystemsPerPage;
                    var createdMeasures = 0;
                    
                    while (createdMeasures < measuresCount)
                    {
                        var pageCmd = new ViewModel.NewPageCommand(score.UndoStack, score.RedoStack)
                        {
                            PageName = "Page " + ++pages,
                            Parent = score
                        };
                        var sizeCmd = new ViewModel.SetterCommand<double, double>(score.UndoStack, score.RedoStack)
                        {
                            Property1 = "Width",
                            Value1 = PART_Width.Value,
                            Property2 = "Height",
                            Value2 = PART_Height.Value
                        };
                        sizeCmd.BindParameterToCommand("Target", pageCmd, "CreatedPage");

                        var lastMeasure = Math.Min(composition.LastMeasure, createdMeasures + measuresPerPage + 1);
                        var sectionCmd = new ViewModel.NewMusicSectionCommand(score.UndoStack, score.RedoStack)
                        {
                            Composition = composition.Composition,
                            FirstMeasure = (ushort)(createdMeasures + 1),
                            LastMeasure = (ushort)lastMeasure,
                            Parameter = composition.MeasuresPerSystem,
                            PresentedParts = composition.Parts
                        };
                        sectionCmd.BindParameterToCommand("Owner", pageCmd, "CreatedPage");
                        var sectionSizeCmd = new ViewModel.SetterCommand<double, double>(score.UndoStack, score.RedoStack)
                        {
                            Property1 = "Width",
                            Value1 = PART_Width.Value,
                            Property2 = "Height",
                            Value2 = PART_Height.Value
                        };
                        sectionSizeCmd.BindParameterToCommand("Target", sectionCmd, "CreatedSection");
                        var sectionPositionCmd = new ViewModel.SetterCommand<double, double>(score.UndoStack, score.RedoStack)
                        {
                            Property1 = "Left",
                            Value1 = 0,
                            Property2 = "Top",
                            Value2 = 0
                        };
                        sectionPositionCmd.BindParameterToCommand("Target", sectionCmd, "CreatedSection");

                        generationMacro.Commands.Add(sizeCmd);
                        generationMacro.Commands.Add(sectionPositionCmd);
                        generationMacro.Commands.Add(sectionSizeCmd);

                        createdMeasures = lastMeasure;
                    }
                }

                generationMacro.Execute(null);
            }

            UndoableCommandBase commit;
            score.CommitChanges(out commit);
            if (commit != null)
            {
                if (IsNewScore)
                    commit.UseStacks = false;
                commit.Execute(null);
            }

            this.DialogResult = true;
        }

        /// <summary>
        /// Handles the Click event of the PART_Cancel control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void PART_Cancel_Click(object sender, RoutedEventArgs e)
        {
            var score = (ViewModel.Elements.Score)DataContext;
            score.AbortChanges();

            this.DialogResult = false;
        }

        /// <summary>
        /// Handles the Click event of the PART_AddComposition control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void PART_AddComposition_Click(object sender, RoutedEventArgs e)
        {
            if (PART_Compositions.SelectedItem is ViewModel.Elements.Composition)
            {
                PresentedCompositions.Add(new CompositionInfo(PART_Compositions.SelectedItem as ViewModel.Elements.Composition));
                PART_SelectedCompositions.SelectedIndex = PresentedCompositions.Count - 1;
            }
        }

        /// <summary>
        /// Handles the Click event of the PART_RemoveComposition control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void PART_RemoveComposition_Click(object sender, RoutedEventArgs e)
        {
            if (PART_SelectedCompositions.SelectedItem != null)
                PresentedCompositions.Remove(PART_SelectedCompositions.SelectedItem as CompositionInfo);
        }

        /// <summary>
        /// Occurs when a part has been selected to be presented in the created music section.
        /// </summary>
        /// <param name="sender">Object where the event handler is attached</param>
        /// <param name="e">Event data</param>
        private void CheckBox_Checked(object sender, RoutedEventArgs e)
        {
            var part = ((CheckBox)sender).DataContext as ViewModel.Elements.PartDescriptor;
            var composition = PART_SelectedCompositions.SelectedItem as CompositionInfo;
            composition.Parts.Add(part);
        }

        /// <summary>
        /// Occurs when a part has been removed from the created music section.
        /// </summary>
        /// <param name="sender">Object where the event handler is attached</param>
        /// <param name="e">Event data</param>
        private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
        {
            var part = ((CheckBox)sender).DataContext as ViewModel.Elements.PartDescriptor;
            var composition = PART_SelectedCompositions.SelectedItem as CompositionInfo;
            composition.Parts.Remove(part);
        }

        /// <summary>
        /// Handles the SelectedItemChanged event of the PART_Compositions control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedPropertyChangedEventArgs{T}"/> of <see cref="object"/> instance containing the event data.</param>
        private void PART_Compositions_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            PART_AddComposition.IsEnabled = PART_Compositions.SelectedItem is ViewModel.Elements.Composition;
        }

        /// <summary>
        /// Handles the SelectionChanged event of the PART_SelectedCompositions control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param>
        private void PART_SelectedCompositions_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            PART_RemoveComposition.IsEnabled = PART_FirstMeasure.IsEnabled =
                PART_LastMeasure.IsEnabled = PART_MeasuresPerSystem.IsEnabled =
                PART_SystemsPerPage.IsEnabled = PART_SelectedCompositions.SelectedItem is CompositionInfo;
            PART_MoveCompositionUp.IsEnabled = PART_SelectedCompositions.SelectedIndex > 0;
            PART_MoveCompositionDown.IsEnabled = PART_SelectedCompositions.SelectedIndex >= 0 && PART_SelectedCompositions.SelectedIndex < PresentedCompositions.Count - 1;

            var composition = PART_SelectedCompositions.SelectedItem as CompositionInfo;
            if (composition != null)
            {
                PART_FirstMeasure.Value = composition.FirstMeasure;
                PART_LastMeasure.Value = composition.LastMeasure;
                PART_MeasuresPerSystem.Value = composition.MeasuresPerSystem;
                PART_SystemsPerPage.Value = composition.SystemsPerPage;
            }
        }

        /// <summary>
        /// Handles the Click event of the PART_MoveCompositionUp control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void PART_MoveCompositionUp_Click(object sender, RoutedEventArgs e)
        {
            PresentedCompositions.Move(PART_SelectedCompositions.SelectedIndex, PART_SelectedCompositions.SelectedIndex - 1);
            PART_MoveCompositionUp.IsEnabled = PART_SelectedCompositions.SelectedIndex > 0;
            PART_MoveCompositionDown.IsEnabled = PART_SelectedCompositions.SelectedIndex >= 0 && PART_SelectedCompositions.SelectedIndex < PresentedCompositions.Count - 1;
        }

        /// <summary>
        /// Handles the Click event of the PART_MoveCompositionDown control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void PART_MoveCompositionDown_Click(object sender, RoutedEventArgs e)
        {
            PresentedCompositions.Move(PART_SelectedCompositions.SelectedIndex, PART_SelectedCompositions.SelectedIndex + 1);
            PART_MoveCompositionUp.IsEnabled = PART_SelectedCompositions.SelectedIndex > 0;
            PART_MoveCompositionDown.IsEnabled = PART_SelectedCompositions.SelectedIndex >= 0 && PART_SelectedCompositions.SelectedIndex < PresentedCompositions.Count - 1;
        }

        /// <summary>
        /// Handles the MouseDoubleClick event of the PART_Compositions control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
        private void PART_Compositions_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (PART_Compositions.SelectedItem is ViewModel.Elements.Composition)
            {
                PresentedCompositions.Add(new CompositionInfo(PART_Compositions.SelectedItem as ViewModel.Elements.Composition));
                PART_SelectedCompositions.SelectedIndex = PresentedCompositions.Count - 1;
            }
        }

        /// <summary>
        /// Handles the ValueChanged event of the PART_FirstMeasure control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedPropertyChangedEventArgs{T}"/> of <see cref="double"/> instance containing the event data.</param>
        private void PART_FirstMeasure_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            var composition = PART_SelectedCompositions.SelectedItem as CompositionInfo;
            if (composition != null)
                composition.FirstMeasure = (ushort)PART_FirstMeasure.Value;
        }

        /// <summary>
        /// Handles the ValueChanged event of the PART_LastMeasure control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedPropertyChangedEventArgs{T}"/> of <see cref="double"/> instance containing the event data.</param>
        private void PART_LastMeasure_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            var composition = PART_SelectedCompositions.SelectedItem as CompositionInfo;
            if (composition != null)
                composition.LastMeasure = (ushort)PART_LastMeasure.Value;
        }

        /// <summary>
        /// Handles the ValueChanged event of the PART_MeasuresPerSystem control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedPropertyChangedEventArgs{T}"/> of <see cref="double"/> instance containing the event data.</param>
        private void PART_MeasuresPerSystem_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            var composition = PART_SelectedCompositions.SelectedItem as CompositionInfo;
            if (composition != null)
                composition.MeasuresPerSystem = (int)PART_MeasuresPerSystem.Value;
        }

        /// <summary>
        /// Handles the ValueChanged event of the PART_SystemsPerPage control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedPropertyChangedEventArgs{T}"/> of <see cref="double"/> instance containing the event data.</param>
        private void PART_SystemsPerPage_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            var composition = PART_SelectedCompositions.SelectedItem as CompositionInfo;
            if (composition != null)
                composition.SystemsPerPage = (int)PART_SystemsPerPage.Value;
        }

        /// <summary>
        /// Handles the Loaded event of the Window.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            if (!IsNewScore)
                PART_Generate.IsChecked = true;
        }

        #endregion
    }

    /// <summary>
    /// Describes one composition that will be presented in the created score.
    /// </summary>
    public class CompositionInfo : INotifyPropertyChanged
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="CompositionInfo"/> class.
        /// </summary>
        /// <param name="composition">
        /// <para>
        /// Type: <see cref="Composition"/>
        /// </para>
        /// <para>
        /// The composition.
        /// </para>
        /// </param>
        public CompositionInfo(ViewModel.Elements.Composition composition)
        {
            this.composition = composition;
            this.firstMeasure = 1;
            this.lastMeasure = (ushort)composition.MeasuresCount;
            this.measuresPerSystem = 4;
            this.systemsPerPage = 4;
            this.parts = new ObservableCollection<ViewModel.Elements.PartDescriptor>(composition.Parts);
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the composition.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ViewModel.Elements.Composition"/>
        /// </para>
        /// <para>
        /// The presented composition.
        /// </para>
        /// </value>
        public ViewModel.Elements.Composition Composition
        {
            get { return composition; }
        }

        /// <summary>
        /// Gets or sets the last measure.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The number of the last presented measure of the composition.
        /// </para>
        /// </value>
        public ushort FirstMeasure
        {
            get { return firstMeasure; }
            set
            {
                firstMeasure = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("FirstMeasure"));
            }
        }

        /// <summary>
        /// Gets or sets the last measure.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The number of the last presented measure of the composition.
        /// </para>
        /// </value>
        public ushort LastMeasure
        {
            get { return lastMeasure; }
            set
            {
                lastMeasure = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("LastMeasure"));
            }
        }

        /// <summary>
        /// Gets or sets the number of measures per system.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The number of measures per system.
        /// </para>
        /// </value>
        public int MeasuresPerSystem
        {
            get { return measuresPerSystem; }
            set
            {
                measuresPerSystem = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("MeasuresPerSystem"));
            }
        }

        /// <summary>
        /// Gets the collection of presented parts.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ObservableCollection{T}"/> of <see cref="ViewModel.Elements.PartDescriptor"/>
        /// </para>
        /// <para>
        /// The collection of parts that will be presented in the score.
        /// </para>
        /// </value>
        public ObservableCollection<ViewModel.Elements.PartDescriptor> Parts
        {
            get { return parts; }
        }

        /// <summary>
        /// Gets or sets the number of systems per page.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The number of systems per page.
        /// </para>
        /// </value>
        public int SystemsPerPage
        {
            get { return systemsPerPage; }
            set
            {
                systemsPerPage = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("SystemsPerPage"));
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Composition"/> property.
        /// </summary>
        private ViewModel.Elements.Composition composition;
        /// <summary>
        /// Holds the value of the <see cref="FirstMeasure"/> property.
        /// </summary>
        private ushort firstMeasure;
        /// <summary>
        /// Holds the value of the <see cref="LastMeasure"/> property.
        /// </summary>
        private ushort lastMeasure;
        /// <summary>
        /// Holds the value of the <see cref="MeasuresPerSystem"/> property.
        /// </summary>
        private int measuresPerSystem;
        /// <summary>
        /// Holds the value of the <see cref="Parts"/> property.
        /// </summary>
        private ObservableCollection<ViewModel.Elements.PartDescriptor> parts;
        /// <summary>
        /// Holds the value of the <see cref="SystemsPerPage"/> property.
        /// </summary>
        private int systemsPerPage;

        #endregion
    }
}
